// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: qiang

#include "coin2/exchange/crypto_v1/order_v1/parser.h"

#include <utility>

#include "coin/proto/coin_query.pb.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::crypto_v1::order_v1 {

namespace {

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;


inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("client_oid")) {
    std::string proc_str = data["client_oid"].GetString();
    try {
      proc_order_id = stoll(proc_str);
    } catch (std::exception& e) {
      LOG(ERROR) << e.what() << proc_str;
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderErrorCode GetOrderErrorCodeFromCryptoFuture(const JsonValue& doc) {
  using coin::proto::OrderErrorCode;
  auto result = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
  std::string msg;
  int64_t code = std::numeric_limits<int64_t>::max();
  if (doc.HasMember("code")) {
    code = stoll(doc["code"].GetString());
  }
  if (doc.HasMember("message")) {
    msg = doc["message"].GetString();
  }

  if (code == 0 || msg == "NO_ERROR") {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (code == 204 || msg == "DUPLICATE_CLORDID") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (code == 213 || msg == "Invalid quantity format") {
    result = OrderErrorCode::SUBMIT_ORDER_WRONG_QTY;
  } else if (code == 308 || msg == "Invalid price format" ) {
    result = OrderErrorCode::SUBMIT_ORDER_WRONG_PRICE;
  } else {
    LOG(ERROR) << "Unknown error code: " << code;
  }

  return result;
}

}  // namespace

bool CryptoParser::IsCryptoFuturesErrMessage(const JsonValue& data) const {
  /*
  {
    "method": "...",
    "code": 0,
    "result": {
      ...
    }
  }
  or
  {
    "code":"100001",
    "msg":"SYS_ERROR"
  }
  */
  std::string code = "1";
  if (data.HasMember("code")) {
    code = data["code"].GetString();
  }
  return code != "0";
}

auto CryptoParser::GetOrderSide(std::string_view side) const -> OrderSide {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto CryptoParser::GetOrderType(std::string_view type) const -> OrderType {
  if (type == "LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void CryptoParser::SetOrderStatus(const JsonValue& data, ProductOrderElement* product_order_element)
    const {
  // private/get-open-orders  NEW PENDING ACTIVE
  // private/get-order-history  REJECTED CANCELED FILLED EXPIRED
  std::string_view status = data["status"].GetString();
  if (status == "NEW" || status == "PENDING" || status == "ACTIVE") {
    product_order_element->set_is_live(true);
  } else if (status == "REJECTED" || status == "EXPIRED") {
    product_order_element->set_is_live(false);
  } else if (status == "FILLED") {
    product_order_element->set_fully_filled(true);
  } else if (status == "CANCELED") {
    product_order_element->set_cancel_confirmed(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

void CryptoParser::ParseSubmitResponse() {
  /*
    {
    "id": 1,
    "method": "private/create-order",
    "code": 0,
    "result": {
        "client_oid": "c5f682ed-7108-4f1c-b755-972fcdca0f02",
        "order_id": "18342311"
      }
    }
  */
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }
  const auto* order_context = msg_->oc;

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[CryptoFuturesSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[CryptoFuturesSubmitOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject()) {
    LOG(ERROR) << "[CryptoFuturesSubmitOrder] failed. invalid format. " << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  if (IsCryptoFuturesErrMessage(document) || !document.HasMember("result") || !document["result"].HasMember("order_id")) {
    LOG(ERROR) << "[CryptoFuturesSubmitOrder] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    auto&& error_code = document["code"].GetString();
    auto&& error_msg = document["message"].GetString();
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    resp->set_error_code(GetOrderErrorCodeFromCryptoFuture(document));
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(document["result"]["order_id"].GetString());
}

void CryptoParser::ParseCancelResponse() {
  /*
    {
    "id": 1,
    "method": "private/cancel-order",
    "code": 0,
    "message": "NO_ERROR",
    "result": {
        "client_oid": "c5f682ed-7108-4f1c-b755-972fcdca0f02",
        "order_id": "18342311"
      }
    }
  */

  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }
  const auto* order_context = msg_->oc;

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus Error";
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject()) {
    LOG(ERROR) << "[ParseCancelResponse] failed. invalid format. " << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  if (IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseCancelResponse] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    std::string error_code = document["code"].GetString();
    std::string error_msg = document["message"].GetString();
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_cancel_confirmed(false);
}

void CryptoParser::ParseRestOrderInfo(
    const JsonValue& data,
    ProductOrderElement* product_order_element) {
  /*
  {
     "account_id": "d75d3e1b-66ef-4fa1-9116-c4a09b858348",
        "order_id": "5755600326572123937",
        "client_oid": "1667448416058584977",
        "order_type": "LIMIT",
        "time_in_force": "GOOD_TILL_CANCEL",
        "side": "SELL",
        "exec_inst": [
          "POST_ONLY"
        ],
        "quantity": "0.1",					
        "limit_price": "1.6319",
        "order_value": "0.16319",
        "avg_price": "0",
        "trigger_price": "0",
        "ref_price": "0",
        "cumulative_quantity": "0",
        "cumulative_value": "0",
        "cumulative_fee": "0",
        "status": "ACTIVE",
        "update_user_id": "d75d3e1b-66ef-4fa1-9116-c4a09b858348",
        "order_date": "2022-11-03",
        "instrument_name": "EOS_USD",
        "fee_instrument_name": "USD_Stable_Coin",
        "create_time": 1667448737534,
        "create_time_ns": "1667448737534795629",
        "update_time": 1667448737534
  }
  */
  product_order_element->set_market_type(MarketType::Spot);
  product_order_element->set_exchange_type(ExchangeType::Crypto);
  product_order_element->set_api_version("v1");
  product_order_element->set_exchange_order_id(data["order_id"].GetString());
  product_order_element->set_symbol(data["instrument_name"].GetString());
  if (data.HasMember("limit_price")){ // MARKET has no limit_price field
    product_order_element->set_price(stod(data["limit_price"].GetString()));
  }

  product_order_element->set_qty(stod(data["quantity"].GetString()));
  product_order_element->set_proc_order_id(GetProcOrderId(data));
  product_order_element->set_side(GetOrderSide(data["side"].GetString()));
  product_order_element->set_order_type(GetOrderType(data["order_type"].GetString()));
  product_order_element->set_timestamp(msg_->timestamp);
  SetOrderStatus(data, product_order_element);
}

void CryptoParser::ParseRestFillOrders() {
/*
{
    "id": 1,
    "method": "private/get-trades",
    "code": 0,
    "result": {
        "data": [{
          ...
        }]
    }
}
*/

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillOrders] failed. " << JsonToString(document);
    return;
  }

  auto pfb = result_.mutable_fill_list();
  pfb->Clear();

  if (ParseRestFillInfo(document, pfb)) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

bool CryptoParser::ParseRestFillInfo(
    const JsonValue& data,
    ProductFillBundle* product_fill_bundle) {
  /*
{
  "account_id": "52e7c00f-1324-5a6z-bfgt-de445bde21a5",
  "event_date": "2021-02-17",
  "journal_type": "TRADING",
  "traded_quantity": "0.0500",
  "traded_price": "51278.5",
  "fees": "-1.025570",
  "order_id": "19708564",
  "trade_id": "38554669",
  "trade_match_id": "76423",
  "client_oid": "7665b001-2753-4d17-b266-61ecb755922d",
  "taker_side": "MAKER",
  "side": "BUY",
  "instrument_name": "BTCUSD-PERP",
  "fee_instrument_name": "USD_Stable_Coin",
  "create_time": 1613570791060,
  "create_time_ns": "1613570791060827635"
}
  */
  if (!data.HasMember("result")) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [result]" << JsonToString(data);
    return false;
  }
  auto&& obj = data["result"].GetObject();
  if (!obj.HasMember("data") || !obj["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [data]" << JsonToString(data);
    return false;
  }
  for (const auto& info : obj["data"].GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(native_symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(info["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(info));    
    fill->set_price(stod(info["traded_price"].GetString()));
    fill->set_qty(stod(info["traded_quantity"].GetString()));
    fill->set_fill_id(info["trade_id"].GetString());

    auto native_currency = GetNativeCurrency(info["fee_instrument_name"].GetString());
    if (symcache_->has_currency_native(native_currency)) {
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      fill->set_fee_currency(currency->currency());
      fill->set_fee(std::abs(stod(info["fees"].GetString())));
    }

    using FillType = coin::proto::FillType;
    std::string fill_type = info["taker_side"].GetString();
    if (fill_type == "MAKER") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "TAKER") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
  }
  return true;
}

void CryptoParser::ParseRestOpenOrders() {
  /*
  {
  "id": 10,
  "method": "private/get-open-orders",
  "code": 0,
  "result": {
    "data": [
      {
        ...
      }
    ]
  }
}
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (document.IsObject() && IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [result]." << JsonToString(document);
    return;
  }
  auto&& obj = document["result"].GetObject();
  if (!obj.HasMember("data") || !obj["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [data]."
               << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  for (const auto& info : obj["data"].GetArray()) {
    if (!symcache_->has_product_native(info["instrument_name"].GetString())) {
      continue;
    }
    auto order = pob->add_each_order();
    ParseRestOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void CryptoParser::ParseRestDoneOrders() {
  /*
    {
    "id": 1,
    "method": "private/get-order-history",
    "code": 0,
    "result": {
        "data": [
          {
            ...
          }
        ]
    }
}
  */


  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestDoneOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (!document.IsObject() || IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. missing field [result]." << JsonToString(document);
    return;
  }
  auto&& obj = document["result"].GetObject();
  if (!obj.HasMember("data") || !obj["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. missing field [data]."
               << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  for (const auto& info : obj["data"].GetArray()) {
    if (!symcache_->has_product_native(info["instrument_name"].GetString())) {
      continue;
    }
    auto order = pob->add_each_order();
    ParseRestOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

auto CryptoParser::ParseBalance(const JsonValue& document) -> AccountBalance {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(document, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Crypto");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool CryptoParser::ParseRawBalance(const JsonValue& document, RawBalanceMap* raw_balance_map) {
 /*
 {
  "id": 10,
  "method": "private/user-balance",
  "code": 0,
  "result": {
    "data": [
      {
        "total_available_balance": "0.00013094",
        "total_margin_balance": "0.00013094",
        "total_initial_margin": "0",
        "total_maintenance_margin": "0",
        "total_position_cost": "0",
        "total_cash_balance": "0.08927548",
        "total_collateral_value": "0.000130923",
        "total_session_unrealized_pnl": "0",
        "instrument_name": "USD_Stable_Coin",
        "total_session_realized_pnl": "0",
        "position_balances": [
          {
            "quantity": "0.0000000088",
            "reserved_qty": "0",
            "collateral_weight": "0.9",
            "collateral_amount": "0.00013092",
            "market_value": "0.00014547",
            "max_withdrawal_balance": "0",
            "instrument_name": "BTC",
            "hourly_interest_rate": "0"
          }
        ],
        "total_effective_leverage": "0",
        "position_limit": "3000000",
        "used_position_limit": "0",
        "total_borrow": "0",
        "margin_score": "0",
        "is_liquidating": false
      }
    ]
  }
}
*/
  if (!document.IsObject() || IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRawBalance] failed. " << JsonToString(document);
    return false;
  }
  if (!document.HasMember("result") || !document["result"].HasMember("data") ) {
    LOG(ERROR) << "[ParseRawBalance] failed. " << JsonToString(document);
    return false;
  }
  const auto& data = document["result"]["data"];
  if (!data.IsArray()) {
    LOG(ERROR) << "[ParseRawBalance] failed. " << JsonToString(document);
    return false;
  }

  for (const auto& info : data.GetArray()) {
    if (!info.HasMember("position_balances") || !info["position_balances"].IsArray()){
      LOG(ERROR) << "[ParseRawBalance] balances failed. " << JsonToString(document);
      continue;
    }

    for (const auto & balance : info["position_balances"].GetArray()) {
      const auto& currency_str = GetNativeCurrency(balance["instrument_name"].GetString());
      CurrencyBalance currency_balance;
      currency_balance.set_currency_native(currency_str);
      auto total = stod(balance["quantity"].GetString());
      auto hold = stod(balance["reserved_qty"].GetString());
      currency_balance.set_total(total);
      currency_balance.set_available(total - hold);
      currency_balance.set_hold(hold);
      raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
    }
  }

  return true;
}

void CryptoParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

void CryptoParser::ParseBalanceFromWs(const JsonValue& data) {
  /*
  [
    {
    ...
    "position_balances": [
      {
        "instrument_name": "CRO",
        "quantity": "24422.72427884",
        "market_value": "4776.107959969951",
        "collateral_amount": "4537.302561971453",
        "collateral_weight": "0.95",
        "max_withdrawal_balance": "24422.72427884"
      }
    ]
    }
  ]
   */
  if (!is_account_balance_initialized_) {
    result_.set_type(MsgType::IGNORABLE);
    return;
  }

  if (!data.IsArray()){
    LOG(ERROR) << "[ParseBalanceFromWs] Parsing Error . Invalid format " << JsonToString(data);
  }

  result_.set_type(MsgType::ACCOUNT_BALANCE);
  auto* acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Crypto");
  acc_balance->set_market_type("Spot");

  bool is_account_balance_found = false;

  for (const auto& infos : data.GetArray()){
    if (!infos.HasMember("position_balances")){
      LOG(ERROR) << "[ParseBalanceFromWs] Parsing Error . Invalid format " << JsonToString(data);
    }

    for (const auto& balances_info : infos["position_balances"].GetArray()) {
      std::string native_currency = GetNativeCurrency(balances_info["instrument_name"].GetString());
      if (!symcache_->has_currency_native(native_currency)) {
        continue;
      }
      auto* balance = acc_balance->add_each_balance();
      auto total = stod(balances_info["quantity"].GetString());
      auto hold = stod(balances_info["reserved_qty"].GetString());
      balance->set_total(total);
      balance->set_available(total - hold);
      balance->set_hold(hold);
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      balance->set_currency(currency->currency());
      balance->set_currency_native(currency->native_currency());
      is_account_balance_found = true;
    }
  }

  if (!is_account_balance_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}

void CryptoParser::ParseOrderFromWs(const JsonValue& data) {
  /*
  "data": [{
      "account_id": "52e7c00f-1324-5a6z-bfgt-de445bde21a5",
      "order_id": "19848525",
      "client_oid": "1613571154900",
      "order_type": "LIMIT",
      "time_in_force": "GOOD_TILL_CANCEL",
      "side": "BUY",
      "exec_inst": [],
      "quantity": "0.0100",
      "limit_price": "50000.0",
      "order_value": "500.000000",
      "maker_fee_rate": "0.000250",
      "taker_fee_rate": "0.000400",
      "avg_price": "0.0",
      "cumulative_quantity": "0.0000",
      "cumulative_value": "0.000000",
      "cumulative_fee": "0.000000",
      "status": "ACTIVE",
      "update_user_id": "fd797356-55db-48c2-a44d-157aabf702e8",
      "order_date": "2021-02-17",
      "instrument_name": "BTCUSD-PERP",
      "fee_instrument_name": "USD_Stable_Coin",
      "create_time": 1613575617173,
      "create_time_ns": "1613575617173123456",
      "update_time": 1613575617173
  }]
   */
  result_.set_type(MsgType::ORDER_STATUS);
  auto* pob = result_.mutable_order_list();
  pob->Clear();

  bool product_order_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto* order = pob->add_each_order();
    order->set_market_type(MarketType::Spot);
    order->set_exchange_type(ExchangeType::Crypto);
    order->set_api_version("v1");
    order->set_exchange_order_id(info["order_id"].GetString());
    order->set_symbol(native_symbol);
    if (info.HasMember("limit_price")){
      order->set_price(stod(info["limit_price"].GetString()));
    }
    order->set_qty(stod(info["quantity"].GetString()));
    order->set_proc_order_id(GetProcOrderId(info));
    order->set_side(GetOrderSide(info["side"].GetString()));
    order->set_order_type(GetOrderType(info["order_type"].GetString()));
    order->set_timestamp(msg_->timestamp);
    SetOrderStatus(info, order);
    product_order_bundle_found = true;
  }
  if (!product_order_bundle_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}


void CryptoParser::ParseTradeFromWs(const JsonValue& data) {
  /*
  "data": [{
    "account_id": "52e7c00f-1324-5a6z-bfgt-de445bde21a5",
    "event_date": "2021-02-17",
    "journal_type": "TRADING",
    "traded_quantity": "0.0500",
    "traded_price": "51278.5",
    "fees": "-1.025570",
    "order_id": "19708564",
    "trade_id": "38554669",
    "trade_match_id": "76423",
    "client_oid":"6ac2421d-5078-4ef6-a9d5-9680602ce123",
    "taker_side":"MAKER",
    "side": "BUY",
    "instrument_name": "BTCUSD-PERP",
    "fee_instrument_name": "USD_Stable_Coin",
    "create_time": 1613570791060,
    "create_time_ns": "1613570791060123456"
  }]
   */
  result_.set_type(MsgType::ORDER_FILL);
  auto* pfb = result_.mutable_fill_list();
  pfb->Clear();

  bool product_fill_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto* fill = pfb->add_each_fill();
    fill->set_symbol(native_symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(info["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(info));
    fill->set_price(stod(info["traded_price"].GetString()));
    fill->set_qty(stod(info["traded_quantity"].GetString()));
    fill->set_fill_id(info["trade_id"].GetString());
    auto native_currency = GetNativeCurrency(info["fee_instrument_name"].GetString());
    if (symcache_->has_currency_native(native_currency)) {
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      fill->set_fee_currency(currency->currency());
      fill->set_fee(std::abs(stod(info["fees"].GetString())));
    }

    using FillType = coin::proto::FillType;
    std::string fill_type = info["taker_side"].GetString();
    if (fill_type == "MAKER") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "TAKER") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }
  if (!product_fill_bundle_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}

void CryptoParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!document.IsObject() || IsCryptoFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseWsMessage] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(WARNING) << "[ParseWsMessage] ignored. " << JsonToString(document);
    return;
  }

  std::string channel = document["result"]["channel"].GetString();
  if (StringStartsWith(channel, "user.balance")) {
    ParseBalanceFromWs(document["result"]["data"]);
  } else if (StringStartsWith(channel, "user.order")) {
    ParseOrderFromWs(document["result"]["data"]);
    } else if (StringStartsWith(channel, "user.trade")) {
    ParseTradeFromWs(document["result"]["data"]);
  } else {
    LOG(ERROR) << "[ParseWsMessage] unknown channel. " << JsonToString(document);
  }
}


auto CryptoParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  // must REST_MESSAGE
  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      // pass
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrders();
      break;
     case OrderSubTopicId::REST_DONE_ORDERS:
      ParseRestDoneOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrders();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    default:
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      break;
  }

  return result_;
}

}  // namespace coin2::exchange::crypto::order_v2
